package libnet

import (
	"container/list"
	"fury/gateway/syncs"
	"net"
	"sync/atomic"
	"time"

	log "github.com/cihub/seelog"
)

type Session struct {
	id   uint64
	conn net.Conn      //当前连接
	time time.Duration //连接时间

	// About send and receive
	readMutex           syncs.Mutex
	sendMutex           syncs.Mutex
	asyncSendChan       chan asyncMessage
	asyncSendBufferChan chan asyncBuffer

	// About session close
	closeChan       chan int
	closeFlag       int32
	closeEventMutex syncs.Mutex
	closeCallbacks  *list.List
}

type closeCallback struct {
	Handler interface{}
	Func    func()
}

type asyncMessage struct {
	C chan<- error
	E Encoder
}

type asyncBuffer struct {
	C chan<- error
	B *OutBuffer
}

// Async work.
type AsyncWork struct {
	c <-chan error
}

type Decoder func(*InBuffer) error
type Encoder func(*OutBuffer) error

func NewSession(id uint64, conn net.Conn, time time.Duration) *Session {
	session := &Session{
		id:                  id,
		conn:                conn,
		time:                time,
		asyncSendChan:       make(chan asyncMessage, sendChanSize),
		asyncSendBufferChan: make(chan asyncBuffer, sendChanSize),
		closeChan:           make(chan int),
		closeCallbacks:      list.New(),
	}
	return session
}

func (session *Session) AddClosedCallback(handle interface{}, callback func()) {
	if session.IsClosed() {
		return
	}
	session.closeEventMutex.Lock()
	defer session.closeEventMutex.Unlock()

	session.closeCallbacks.PushBack(closeCallback{handle, callback})
}

// Get session id.
func (session *Session) Id() uint64 {
	return session.id
}

// Get session connection.
func (session *Session) Conn() net.Conn {
	return session.conn
}

// Check session is closed or not.
func (session *Session) IsClosed() bool {
	return atomic.LoadInt32(&session.closeFlag) != 0
}

// Close session.
func (session *Session) Close() {
	if atomic.CompareAndSwapInt32(&session.closeFlag, 0, 1) {
		session.conn.Close()

		// exit send loop and cancel async send
		close(session.closeChan)

		session.invokeCloseCallbacks()
	}
}

// Sync send a message. Equals Packet() then SendPacket(). This method will block on IO.
func (session *Session) Send(encoder Encoder) error {
	var err error

	buffer := newOutBuffer()
	//	session.protocol.PrepareOutBuffer(buffer, 1024)

	err = encoder(buffer)
	if err == nil {
		err = session.sendBuffer(buffer)
	}

	buffer.free()
	return err
}

func (session *Session) sendBuffer(buffer *OutBuffer) error {
	session.sendMutex.Lock()
	defer session.sendMutex.Unlock()

	//	return session.protocol.Write(session.conn, buffer)
	return nil
}

// Remove close callback.
func (session *Session) RemoveCloseCallback(handler interface{}) {
	if session.IsClosed() {
		return
	}

	session.closeEventMutex.Lock()
	defer session.closeEventMutex.Unlock()

	for i := session.closeCallbacks.Front(); i != nil; i = i.Next() {
		if i.Value.(closeCallback).Handler == handler {
			session.closeCallbacks.Remove(i)
			return
		}
	}
}

// Dispatch close event.
func (session *Session) invokeCloseCallbacks() {
	session.closeEventMutex.Lock()
	defer session.closeEventMutex.Unlock()

	for i := session.closeCallbacks.Front(); i != nil; i = i.Next() {
		callback := i.Value.(closeCallback)
		callback.Func()
	}
}

// Process one request.
func (session *Session) ProcessOnce(decoder Decoder) error {
	session.readMutex.Lock()
	defer session.readMutex.Unlock()

	buffer := newInBuffer()
	//	err := session.protocol.Read(session.conn, buffer)
	err := session.conn.SetDeadline(time.Now())
	if err != nil {
		log.Error("process Once error")
		buffer.free()
		session.Close()
		return err
	}

	err = decoder(buffer)
	buffer.free()

	return nil
}

// Process request.
func (session *Session) Process(decoder Decoder) error {
	for {
		if err := session.ProcessOnce(decoder); err != nil {
			return err
		}
	}
	return nil
}
